/* An STM32 HAL library written for the LK8563 real-time clock IC. */
/* Library by @eepj www.github.com/eepj */
#include <sys/time.h>
#include <time.h>
#include <string.h>

#include "esp_log.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_check.h"

#include "freertos/FreeRTOS.h"

#include "i2c_bus.h"
#include "lk8563.h"

static const char *TAG = "lk8563";

#define PCF_ALARM_FLAG                  (1<<3)
#define PCF_TIMER_FLAG                  (1<<2)
#define PCF_ALARM_INTERRUPT_ENABLE      (1<<1)
#define PCF_TIMER_INTERRUPT_ENABLE      (1<<0)

#define PCF_CLKOUT_32768HZ              0b10000000
#define PCF_CLKOUT_1024HZ               0b10000001
#define PCF_CLKOUT_32HZ                 0b10000010
#define PCF_CLKOUT_1HZ                  0b10000011
#define PCF_CLKOUT_DISABLED             0b00000000

#define PCF_TIMER_4096HZ                0b10000000
#define PCF_TIMER_64HZ                  0b10000001
#define PCF_TIMER_1HZ                   0b10000010
#define PCF_TIMER_1_60HZ                0b10000011
#define PCF_TIMER_DISABLED              0b00000011

#define PCF_DISABLE_ALARM               80

#define LK8563_REG_CTRL_0                0x00
#define LK8563_REG_CTRL_1                0x01
#define LK8563_REG_SECOND              0x02
#define LK8563_REG_MINUTE              0x03
#define LK8563_REG_HOUR                0x04
#define LK8563_REG_DAY                 0x05
#define LK8563_REG_WEEKDAY             0x06
#define LK8563_REG_MONTH               0x07
#define LK8563_REG_YEAR                0x08


#define BinToBCD(bin) ((((bin) / 10) << 4) + ((bin) % 10))
#define BCDToBin(bin) ((((bin & 0xf0) >> 4) * 10) + (bin & 0x0f))

// #define I2C_LK8563_MAX_TRANS_UNIT (24)
i2c_lk8563_handle_t i2c_dev_lk8563 = NULL;      /*!< I2C device handle */
static esp_err_t last_i2c_err = ESP_OK;

esp_err_t PCF_Init(void *bus_handle, const i2c_lk8563_config_t *lk8563_config, i2c_lk8563_handle_t *lk8563_handle)
{
    esp_err_t ret = ESP_OK;
    i2c_lk8563_t *out_handle;
    out_handle = (i2c_lk8563_t *)calloc(1, sizeof(i2c_lk8563_t));
    ESP_GOTO_ON_FALSE(out_handle, ESP_ERR_NO_MEM, err, TAG, "no memory for i2c lk8563 device");

    if (out_handle->i2c_dev == NULL) {
        out_handle->i2c_dev = i2c_bus_device_create( bus_handle, lk8563_config->dev_addr, lk8563_config->clk_speed);
    }
    if(out_handle->i2c_dev == NULL) {
        ret = ESP_ERR_INVALID_ARG;
        goto err;
    }
    *lk8563_handle = out_handle;

	uint8_t reg0_val = 0b00000000;
	ret = PCF_Write(out_handle, 0x00, &reg0_val, 1);
	if (ret != ESP_OK){
		goto err;
	}
	uint8_t reg1_val = 0b00010011;
	ret = PCF_Write(out_handle, 0x01, &reg1_val, 1);
	if (ret != ESP_OK){
		goto err;
	}

    return ESP_OK;

err:
    if (out_handle && out_handle->i2c_dev) {
        i2c_bus_device_delete(&out_handle->i2c_dev);
    }
    free(out_handle);
    return ret;
}

// static esp_err_t i2c_lk8563_write(i2c_lk8563_handle_t lk8563_handle, uint32_t address, const uint8_t *data, uint32_t size)
// {
//     ESP_RETURN_ON_FALSE(lk8563_handle, ESP_ERR_NO_MEM, TAG, "no mem for buffer");

//     return i2c_bus_write_bytes(lk8563_handle->i2c_dev, address, size, data);
// }

// static esp_err_t i2c_lk8563_read(i2c_lk8563_handle_t lk8563_handle, uint32_t address, uint8_t *data, uint32_t size)
// {
//     ESP_RETURN_ON_FALSE(lk8563_handle, ESP_ERR_NO_MEM, TAG, "no mem for buffer");

//     return i2c_bus_read_bytes(lk8563_handle->i2c_dev, address, size, data);
// }

esp_err_t PCF_Write(i2c_lk8563_handle_t handle, uint8_t addr, uint8_t *data, size_t count) {

	last_i2c_err = i2c_bus_write_bytes(handle->i2c_dev, addr, count, data);
    return last_i2c_err;
}

esp_err_t PCF_Read(i2c_lk8563_handle_t handle, uint8_t addr, uint8_t *data, size_t count) {

	last_i2c_err = i2c_bus_read_bytes(handle->i2c_dev, addr, count, data);
    return last_i2c_err;
}

esp_err_t PCF_GetLastError(){
	return last_i2c_err;
}

int PCF_GetAndClearFlags(i2c_lk8563_handle_t handle){
	uint8_t flags;

	esp_err_t ret = PCF_Read(handle, 0x01, &flags, 1);
	if (ret != ESP_OK){
		return -1;
	}
	uint8_t cleared = flags & 0b00010011;
	ret = PCF_Write(handle, 0x01, &cleared, 1);
	if (ret != ESP_OK){
		return -1;
	}

	return flags & 0x0C;
}

int PCF_SetClockOut(i2c_lk8563_handle_t handle, uint8_t mode){

	mode &= 0b10000011;
	esp_err_t ret = PCF_Write(handle, 0x0D, &mode, 1);
	if (ret != ESP_OK) {
		return -1;
	}
	return 0;	
}

int PCF_SetTimer(i2c_lk8563_handle_t handle, uint8_t mode, uint8_t count){

	mode &= 0b10000011;
	esp_err_t ret = PCF_Write(handle, 0x0E, &mode, 1);
	if (ret != ESP_OK) {
		return -1;
	}
	ret = PCF_Write(handle, 0x0F, &count, 1);
	if (ret != ESP_OK) {
		return -1;
	}
	return 0;
}

int PCF_GetTimer(i2c_lk8563_handle_t handle){
	uint8_t count;

	esp_err_t ret = PCF_Read(handle, 0x0F, &count, 1);
	if (ret != ESP_OK) {
		return -1;
	}
	return (int) count;
}

int PCF_SetAlarm(i2c_lk8563_handle_t handle, PCF_Alarm *alarm){
	if ((alarm->minute >= 60 && alarm->minute != 80) || (alarm->hour >= 24 && alarm->hour != 80) || (alarm->day > 32 && alarm->day != 80) || (alarm->weekday > 6 && alarm->weekday != 80))
	{
		return -2;
	}

	uint8_t buffer[4];

	buffer[0] = BinToBCD(alarm->minute) & 0xFF;
	buffer[1] = BinToBCD(alarm->hour) & 0xBF;
	buffer[2] = BinToBCD(alarm->day) & 0xBF;
	buffer[3] = BinToBCD(alarm->weekday) & 0x87;

	esp_err_t ret = PCF_Write(handle, 0x09, buffer, sizeof(buffer));
	if (ret != ESP_OK) {
		return -1;
	}

	return 0;
}

int PCF_GetAlarm(i2c_lk8563_handle_t handle, PCF_Alarm *alarm) {
	uint8_t buffer[4];

	esp_err_t ret = PCF_Read(handle, 0x09, buffer, sizeof(buffer));
	if (ret != ESP_OK) {
		return -1;
	}

	alarm->minute = (((buffer[0] >> 4) & 0x0F) * 10) + (buffer[0] & 0x0F);
	alarm->hour = (((buffer[1] >> 4) & 0x0B) * 10) + (buffer[1] & 0x0F);
	alarm->day = (((buffer[2] >> 4) & 0x0B) * 10) + (buffer[2] & 0x0F);
	alarm->weekday = (((buffer[3] >> 4) & 0x08) * 10) + (buffer[3] & 0x07);

	return 0;
}

int PCF_SetDateTime(i2c_lk8563_handle_t handle, PCF_DateTime *dateTime) {
	if (dateTime->second >= 60 || dateTime->minute >= 60 || dateTime->hour >= 24 || dateTime->day > 32 || dateTime->weekday > 6 || dateTime->month > 12 || dateTime->year < 1900 || dateTime->year >= 2100)
	{
		ESP_LOGW(TAG, "Invalid date/time");
		return -2;
	}

	uint8_t buffer[7];

	buffer[0] = BinToBCD(dateTime->second) & 0x7F;
	buffer[1] = BinToBCD(dateTime->minute) & 0x7F;
	buffer[2] = BinToBCD(dateTime->hour) & 0x3F;
	buffer[3] = BinToBCD(dateTime->day) & 0x3F;
	buffer[4] = BinToBCD(dateTime->weekday) & 0x07;
	buffer[5] = BinToBCD(dateTime->month) & 0x1F;

	if (dateTime->year >= 2000)
	{
		buffer[5] |= 0x80;
		buffer[6] = BinToBCD(dateTime->year - 2000);
	}
	else
	{
		buffer[6] = BinToBCD(dateTime->year - 1900);
	}

	esp_err_t ret = PCF_Write(handle, 0x02, buffer, sizeof(buffer));
	if (ret != ESP_OK) {
		return -1;
	}

	return 0;
}

int PCF_GetDateTime(i2c_lk8563_handle_t handle, PCF_DateTime *dateTime) {
	uint8_t buffer[7];
	esp_err_t ret;

	ret = PCF_Read(handle, 0x02, buffer, sizeof(buffer));
	if (ret != ESP_OK) {
		return -1;
	}

	dateTime->second = (((buffer[0] >> 4) & 0x07) * 10) + (buffer[0] & 0x0F);
	dateTime->minute = (((buffer[1] >> 4) & 0x07) * 10) + (buffer[1] & 0x0F);
	dateTime->hour = (((buffer[2] >> 4) & 0x03) * 10) + (buffer[2] & 0x0F);
	dateTime->day = (((buffer[3] >> 4) & 0x03) * 10) + (buffer[3] & 0x0F);
	dateTime->weekday = (buffer[4] & 0x07);
	dateTime->month = ((buffer[5] >> 4) & 0x01) * 10 + (buffer[5] & 0x0F);
	dateTime->year = 1900 + ((buffer[6] >> 4) & 0x0F) * 10 + (buffer[6] & 0x0F);

	if (buffer[5] &  0x80)
	{
		dateTime->year += 100;
	}

	if (buffer[0] & 0x80) //Clock integrity not guaranted
	{
		return 1;
	}

	return 0;
}
/*
	同步pcf8563时钟到系统时间
	return 0:success
	return 1:clock integrity not guaranted
	return -1:i2c error
*/
int PCF_hctosys(){
	int ret;
	PCF_DateTime date = {0};
	struct tm tm = {0};
	struct timeval tv = {0};

	if(i2c_dev_lk8563 == NULL){
		return -1;
	}
    ret = PCF_GetDateTime(i2c_dev_lk8563, &date);
	printf("PCF_GetDateTime %d\n", ret);
    if (ret != 0) {
		goto fail;
    }
	tm.tm_sec = date.second;
	tm.tm_min = date.minute;
	tm.tm_hour = date.hour;
	tm.tm_mday = date.day;
	tm.tm_mon = date.month - 1;
	tm.tm_year = date.year - 1900;

	tv.tv_sec = mktime(&tm);
	tv.tv_usec = 0;
	ret = settimeofday(&tv, NULL);

fail:
	return ret;
}

/*
	同步系统时间到pcf8563时钟
	return 0:success
	return -1:i2c error
*/
int PCF_systohc(){
	int ret;
	PCF_DateTime date = {0};
	struct tm tm = {0};
	if(i2c_dev_lk8563 == NULL){
		return -1;
	}
	time_t now = time(NULL);
	localtime_r(&now, &tm);
	date.second = tm.tm_sec;
	date.minute = tm.tm_min;
	date.hour = tm.tm_hour;
	date.day = tm.tm_mday;
	date.month = tm.tm_mon + 1;
	date.year = tm.tm_year + 1900;
	date.weekday = tm.tm_wday;

	ret = PCF_SetDateTime(i2c_dev_lk8563, &date);

//fail:
	return ret;
}
